import sys
import struct

# major python version
if sys.version_info[0] == 2:
	PY2, PY3 = True, False
elif sys.version_info[0] == 3:
	PY2, PY3 = False, True

def dumps(value):
	if value is None:
		svalue = b'N'
	elif isinstance(value, bool):
		svalue = b'T' if value else b'F'
	elif PY2 and isinstance(value, int):
		svalue = b'i' + struct.pack('!q', value)
	elif PY2 and isinstance(value, long):
		_svalue = str(value)
		svalue = b'l' + struct.pack('!Q', len(_svalue)) + _svalue
	elif PY3 and isinstance(value, int):
		if -2 ** 32 < value < 2 ** 32:
			svalue = b'i' + struct.pack('!q', value)
		else:
			_svalue = str(value)
			svalue = b'l' + struct.pack('!Q', len(_svalue)) + _svalue
	elif isinstance(value, float):
		svalue = b'f' + struct.pack('!d', value)
	elif isinstance(value, complex):
		svalue = b'c' + struct.pack('!dd', value.real, value.imag)
	elif isinstance(value, bytes):
		svalue = b'b' + struct.pack('!Q', len(value)) + str(value)
	elif PY2 and isinstance(value, str):
		svalue = b's' + struct.pack('!Q', len(value)) + value
	elif PY2 and isinstance(value, unicode):
		_svalue = value.encode('utf-8')
		svalue = b'u' + struct.pack('!Q', len(_svalue)) + _svalue
	elif PY3 and isinstance(value, str):
		_svalue = value.encode('utf-8')
		svalue = b's' + struct.pack('!Q', len(_svalue)) + _svalue
	elif isinstance(value, tuple):
		_svalue = b''.join((dumps(v) for v in value))
		svalue = b'p' + struct.pack('!QQ', len(value), len(_svalue)) + _svalue
	elif isinstance(value, list):
		_svalue = b''.join((dumps(v) for v in value))
		svalue = b't' + struct.pack('!QQ', len(value), len(_svalue)) + _svalue
	elif isinstance(value, dict):
		if PY2:
			_svalue = b''.join((dumps(k) + dumps(v) for k, v in value.iteritems()))
		elif PY3:
			_svalue = b''.join((dumps(k) + dumps(v) for k, v in value.items()))
		
		svalue = b'd' + struct.pack('!QQ', len(value), len(_svalue)) + _svalue
	elif isinstance(value, set):
		_svalue = b''.join((dumps(v) for v in value))
		svalue = b'e' + struct.pack('!QQ', len(value), len(_svalue)) + _svalue
	elif isinstance(value, frozenset):
		_svalue = b''.join((dumps(v) for v in value))
		svalue = b'r' + struct.pack('!QQ', len(value), len(_svalue)) + _svalue
	else:
		# state = value.__getstate__()
		# svalue = dumps(state)
		# svalue = b'a' + svalue[1:]
		raise TypeError('unsupported type "%s"' % type(value).__name__)
	
	return svalue

def dump(value, f, pos=0, whence=0):
	svalue = dumps(value)
	f.seek(pos, whence)
	f.write(svalue)

def _loads(svalue, pos):
	if PY2:
		t = svalue[pos]
	elif PY3:
		t = chr(svalue[pos]).encode('utf-8')
	
	if t == b'N':
		value = None
		pos = pos + 1
	elif t == b'T':
		value = True
		pos = pos + 1
	elif t == b'F':
		value = False
		pos = pos + 1
	elif t == b'i':
		value, = struct.unpack('!q', svalue[pos + 1:pos + 9])
		pos = pos + 9
	elif t == b'l':
		value_len, = struct.unpack('!Q', svalue[pos + 1:pos + 9])
		
		if PY2:
			value = long(svalue[pos + 9:pos + 9 + value_len])
		elif PY3:
			value = int(svalue[pos + 9:pos + 9 + value_len])
		
		pos = pos + 9 + value_len
	elif t == b'f':
		value, = struct.unpack('!d', svalue[pos + 1:pos + 9])
		pos = pos + 9
	elif t == b'c':
		real, imag = struct.unpack('!dd', svalue[pos + 1:pos + 17])
		value = ComplexType(real, imag)
		pos = pos + 17
	elif t == b'b':
		value_len, = struct.unpack('!Q', svalue[pos + 1:pos + 9])
		value = bytes(svalue[pos + 9:pos + 9 + value_len])
		pos = pos + 9 + value_len
	elif t == b's':
		value_len, = struct.unpack('!Q', svalue[pos + 1:pos + 9])
		
		if PY2:
			value = svalue[pos + 9:pos + 9 + value_len]
		elif PY3:
			value = svalue[pos + 9:pos + 9 + value_len].decode('utf-8')
		
		pos = pos + 9 + value_len
	elif t == b'u':
		value_len, = struct.unpack('!Q', svalue[pos + 1:pos + 9])
		
		if PY2:
			value = svalue[pos + 9:pos + 9 + value_len].decode('utf-8')
		elif PY3:
			value = svalue[pos + 9:pos + 9 + value_len].decode('utf-8')
		
		pos = pos + 9 + value_len
	elif t == b'p':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _loads(svalue, i)
			value.append(v)
		
		value = tuple(value)
		pos = i
	elif t == b't':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _loads(svalue, i)
			value.append(v)
		
		pos = i
	elif t == b'd':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = {}
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			k, i = _loads(svalue, i)
			v, i = _loads(svalue, i)
			value[k] = v
			
		pos = i
	elif t == b'e':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _loads(svalue, i)
			value.append(v)
		
		value = set(value)
		pos = i
	elif t == b'r':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _loads(svalue, i)
			value.append(v)
		
		value = frozenset(value)
		pos = i
	elif t == b'a':
		items_num, sitems_len = struct.unpack('!QQ', svalue[pos + 1:pos + 17])
		value = {}
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			k, i = _loads(svalue, i)
			v, i = _loads(svalue, i)
			value[k] = v
		
		pos = i
	else:
		raise TypeError('unsupported type "%s"' % svalue[pos])
	
	return value, pos

def loads(svalue):
	value, pos = _loads(svalue, 0)
	return value

def _load(f, pos=0, whence=0):
	f.seek(pos, whence)
	t = bytes(f.read(1))
	
	if t == b'N':
		value = None
		pos = f.tell()
	elif t == b'T':
		value = True
		pos = f.tell()
	elif t == b'F':
		value = False
		pos = f.tell()
	elif t == b'i':
		value, = struct.unpack('!q', f.read(8))
		pos = f.tell()
	elif t == b'l':
		value_len, = struct.unpack('!Q', f.read(8))
		
		if PY2:
			value = long(f.read(value_len))
		elif PY3:
			value = int(f.read(value_len))
		
		pos = f.tell()
	elif t == b'f':
		value, = struct.unpack('!d', f.read(8))
		pos = f.tell()
	elif t == b'c':
		real, imag = struct.unpack('!dd', f.read(16))
		value = complex(real, imag)
		pos = f.tell()
	elif t == b'b':
		value_len, = struct.unpack('!Q', f.read(8))
		value = bytes(f.read(value_len))
		pos = f.tell()
	elif t == b's':
		value_len, = struct.unpack('!Q', f.read(8))
		
		if PY2:
			value = f.read(value_len)
		elif PY3:
			value = f.read(value_len).decode('utf-8')
		
		pos = f.tell()
	elif t == b'u':
		value_len, = struct.unpack('!Q', f.read(8))
		
		if PY2:
			value = f.read(value_len).decode('utf-8')
		elif PY3:
			value = f.read(value_len).decode('utf-8')
		
		pos = f.tell()
	elif t == b'p':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _load(f, i)
			value.append(v)
		
		value = tuple(value)
		pos = i
	elif t == b't':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _load(f, i)
			value.append(v)
		
		pos = i
	elif t == b'd':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = {}
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			k, i = _load(f, i)
			v, i = _load(f, i)
			value[k] = v
			
		pos = i
	elif t == b'e':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _load(f, i)
			value.append(v)
		
		value = set(value)
		pos = i
	elif t == b'r':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = []
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			v, i = _load(f, i)
			value.append(v)
		
		value = frozenset(value)
		pos = i
	elif t == b'a':
		items_num, sitems_len = struct.unpack('!QQ', f.read(16))
		value = {}
		i = pos + 17
		
		while i < pos + 17 + sitems_len:
			k, i = _load(f, i)
			v, i = _load(f, i)
			value[k] = v
		
		pos = i
	else:
		raise TypeError('unsupported type "%s"' % t)
		
	return value, pos

def load(f, pos=0, whence=0):
	value, pos = _load(f, pos, whence)
	return value

if __name__ == '__main__':
	a = dumps({1: [2, '', (1.1)], '2': 2.0})
	print(repr(loads(a)))
	
	f = open('test1.bon', 'wb+')
	a = dump({1: [2, '', (1.1)], '2': 2.0}, f)
	f.close()
	
	f = open('test1.bon', 'rb')
	print(repr(load(open('test1.bon', 'rb'))))
	f.close()
